package cc.shacocloud.mirage.restful;

import cc.shacocloud.mirage.restful.http.HttpHeaderMap;
import cc.shacocloud.mirage.restful.http.MultipartFileUpload;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.LanguageHeader;
import io.vertx.ext.web.ParsedHeaderValue;
import io.vertx.ext.web.ParsedHeaderValues;
import io.vertx.ext.web.Session;
import org.jetbrains.annotations.Nullable;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

/**
 * vertx http服务请求对象，可以直接定义在控制器的方法上注入
 */
public interface HttpRequest {
    
    /**
     * @return 当前的路由上下文
     */
    RoutingContext context();
    
    /**
     * @return HTTP响应对象
     */
    HttpResponse response();
    
    /**
     * @return 返回用于发出此请求的方案的名称，例如http、https或ftp。正如 RFC 1738 中指出的，不同的方案有不同的url构造规则。
     */
    String getScheme();
    
    /**
     * @return 返回发送请求到的服务器的主机名。它是主机头值中“:”之前的值(如果有的话)，或者解析的服务器名称，或者服务器IP地址。
     * 可能为空，例如：<code>Host</code>值不存在，服务器有绑定在域套接字上，则返回{@code null}
     */
    @Nullable
    String getServerName();
    
    /**
     * @return 返回发送请求的端口号。它是主机头<code>Host</code>值中“:”后面的部分的值(如果有的话)，或者是接受客户端连接的服务器端口的值。
     * 如果 <code>Host</code>值不存在，服务器又绑定在域套接字上，则返回 -1
     */
    int getServerPort();
    
    /**
     * @return 请求的HTTP版本
     */
    HttpVersion version();
    
    /**
     * @return 请求的HTTP方法。
     */
    HttpMethod method();
    
    /**
     * 返回当前请求的标题，在{@link HttpServerRequest#headers()} 上的拓展
     */
    HttpHeaderMap headers();
    
    /**
     * 返回具有指定名称的第一个标头值
     */
    @Nullable
    default String getHeader(String headerName) {
        return headers().get(headerName);
    }
    
    /**
     * @return 是否是通过SSL/TLS加密的
     */
    boolean isSSL();
    
    /**
     * @return 请求的URI。这通常是一个相对URI
     */
    String uri();
    
    /**
     * @return HTTP请求对应的绝对URI
     */
    String absoluteURI();
    
    /**
     * @return uri的路径部分
     */
    @Nullable
    String path();
    
    /**
     * @return uri的查询部分
     */
    @Nullable
    String query();
    
    /**
     * @return 请求的主机。对于HTTP2它返回{@literal authority}伪头，否则它返回{@literal host}头
     */
    @Nullable
    String host();
    
    /**
     * @return 为请求体读取的总字节数。
     */
    long bytesRead();
    
    /**
     * @return 此连接的远程地址，可能是{@code null}(例如服务器绑定在域套接字上)
     */
    SocketAddress remoteAddress();
    
    /**
     * @return 此连接的本地地址，可能是{@code null}(例如服务器绑定在域套接字上)
     */
    SocketAddress localAddress();
    
    /**
     * @return SSLSession 与底层套接字关联。如果连接不是 SSL，则返回 null
     * @see javax.net.ssl.SSLSession
     */
    SSLSession sslSession();
    
    /**
     * 注意:Java SE 5+推荐使用javax.net.ssl.SSLSession#getPeerCertificates()，而不是使用此方法所基于的javax.net.ssl.SSLSession#getPeerCertificateChain()。
     * 使用{@link #sslSession()} *访问该方法。
     *
     * @return 对等证书的有序数组。如果连接是而不是SSL，则返回null。
     * @throws javax.net.ssl.SSLPeerUnverifiedException 没有验证SSL对等点的身份
     * @see javax.net.ssl.SSLSession#getPeerCertificateChain()
     * @see #sslSession()
     */
    X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException;
    
    /**
     * 请求结束了吗?即，是否已读取整个请求，包括主体?
     *
     * @return 如果结束则返回 {@code true}
     */
    boolean isEnded();
    
    /**
     * 获取具有指定名称的cookie。
     *
     * @param name cookie的名称
     * @return 饼干
     */
    @Nullable
    Cookie getCookie(String name);
    
    /**
     * @return cookie 的数量。
     */
    int cookieCount();
    
    /**
     * @return Cookie Map
     * @deprecated 该实现做出了一个错误的假设，即只能通过cookie的名称识别它们。RFC声明元组{@code <name, domain, path>}是唯一标识符
     */
    @Deprecated
    Map<String, Cookie> cookieMap();
    
    /**
     * @return 整个 HTTP 请求主体是一个字符串，UTF-8 编码。
     * 上下文必须首先被路由到{@link io.vertx.ext.web.handler.BodyHandler}用于填充此内容。
     */
    @Nullable
    String getBodyAsString();
    
    /**
     * 假设使用指定的编码，将整个HTTP请求主体作为字符串获取。上下文必须首先被路由到
     * {@link io.vertx.ext.web.handler.BodyHandler} 用于填充此内容。
     *
     * @param encoding 编码, e.g. "UTF-16"
     * @return the body
     */
    @Nullable
    String getBodyAsString(String encoding);
    
    /**
     * @return 以 {@link JsonObject}的形式获取整个HTTP请求主体。
     * 上下文必须首先被路由到 {@link io.vertx.ext.web.handler.BodyHandler}用于填充此内容。
     * <br/>
     * 当主体为{@code null}或{@code "null"} JSON文字时，则返回{@code null}。
     */
    @Nullable
    JsonObject getBodyAsJson();
    
    /**
     * @return 以 {@link JsonArray}的形式获取整个HTTP请求主体。
     * 上下文必须首先被路由到 {@link io.vertx.ext.web.handler.BodyHandler}用于填充此内容。
     * <br/>
     * 当主体为{@code null}或{@code "null"} JSON文字时，则返回{@code null}。
     */
    @Nullable
    JsonArray getBodyAsJsonArray();
    
    /**
     * @return 以 {@link Buffer}的形式获取整个HTTP请求体。
     * 上下文必须首先被路由到 {@link io.vertx.ext.web.handler.BodyHandler}用于填充此内容。
     */
    @Nullable
    Buffer getBody();
    
    /**
     * @return 请求的文件集(如果有的话)。上下文必须首先被路由到 {@link io.vertx.ext.web.handler。BodyHandler}使其工作。
     */
    MultipartFileUpload fileUploads();
    
    /**
     * Session 为浏览器会话而存在，并由会话cookie维护。
     *
     * @return the session.
     */
    @Nullable
    Session session();
    
    /**
     * 是否已经调用了{@link io.vertx.ext.web.RoutingContext#session()}。这通常由  {@link io.vertx.ext.web.handler.SessionHandler}使用。
     *
     * @return 如果会话已被访问，则为true。
     */
    boolean isSessionAccessed();
    
    /**
     * 标题:
     * <ol>
     * <li>Accept</li>
     * <li>Accept-Charset</li>
     * <li>Accept-Encoding</li>
     * <li>Accept-Language</li>
     * <li>Content-Type</li>
     * </ol>
     * 解析成 {@link ParsedHeaderValue}
     *
     * @return 具有已解析头部的容器
     */
    ParsedHeaderValues parsedHeaders();
    
    /**
     * 返回当前请求的语言。语言由<code>Accept-Language</code> 标头确定，并按质量排序。
     * <p>
     * 当2个或更多的条目具有相同的质量时，用于返回最佳匹配的顺序是基于原始列表中最低的索引。
     * 例如，如果用户有相同质量的en-US和en-GB，那么这个订单的最佳匹配将是en-US，因为客户机将其声明为第一个条目。
     *
     * @return 请求的最佳匹配语言
     */
    default List<LanguageHeader> acceptableLanguages() {
        return parsedHeaders().acceptLanguage();
    }
    
    /**
     * 返回用户首选语言。
     * 这与返回可接受语言的第一个元素的操作相同。
     *
     * @return 用户首选的区域设置
     */
    default LanguageHeader preferredLanguage() {
        List<? extends LanguageHeader> acceptableLanguages = acceptableLanguages();
        return acceptableLanguages.size() > 0 ? acceptableLanguages.get(0) : null;
    }
    
    /**
     * 返回路径声明中定义的命名参数及其实际值的映射
     *
     * @return 命名参数的映射
     */
    Map<String, String> pathParams();
    
    /**
     * 获取单个路径参数的值
     *
     * @param name 在路径声明中定义的参数名称
     * @return 参数的实际值，如果不存在则为空
     */
    @Nullable
    String pathParam(String name);
    
    /**
     * 属性中所有查询参数的映射 <a href="https://en.wikipedia.org/wiki/Query_string">查询字符串</a>
     * <br/>
     * 查询参数是延迟解码的: 解码在第一次调用此方法时发生。
     *
     * @return 查询参数的多重映射
     */
    MultiMap queryParams();
    
    /**
     * 获取单个查询参数的值。
     *
     * @param name 查询参数的名称
     * @return 列出与参数名匹配的所有参数。如果没有找到{@code name}的查询参数，它将返回一个空列表
     */
    List<String> queryParams(String name);
    
    /**
     * 获取单个查询参数的值。如果匹配的参数存在多个则使用第一个
     *
     * @see #queryParams(String)
     */
    @Nullable
    String queryParam(String name);
    
    
    /**
     * 返回请求中所有表单属性的映射。
     *
     * @return 表单属性
     */
    MultiMap formAttributes();
    
    
    /**
     * 返回具有指定名称的第一个表单属性值
     *
     * @param attributeName 属性名称
     * @return 列出与参数名匹配的所有参数。如果没有找到{@code name}的表单属性，它将返回一个空列表
     */
    List<String> formAttributes(String attributeName);
    
    /**
     * 返回具有指定名称的第一个表单属性值
     *
     * @param attributeName 属性名称
     * @return 属性值
     */
    @Nullable
    String formAttribute(String attributeName);
    
    /**
     * 获取源对象 {@link HttpServerRequest}
     */
    HttpServerRequest getSource();
    
}
